﻿/**
 * Copyright (c) 2013 Nokia Corporation.
 */

using System;
using System.Collections.Generic;
using System.Device.Location;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using System.Windows.Media.Imaging;
using Microsoft.Devices.Sensors;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Maps.Controls;
using Microsoft.Phone.Shell;
using Windows.Devices.Geolocation;

using Dradis.Resources;

namespace Dradis
{
    /// <summary>
    /// The main page of the application.
    /// </summary>
    public partial class MainPage : PhoneApplicationPage
    {
        // Data types
        private enum MyLocationMarkerTypes
        {
            Red = 0,
            Green = 1,
            NoLocation = 2
        };

        // Constants
        private const int CompassUpdateInterval = 40; // Milliseconds (must be multiple of 20)
        private const int LocationUpdateInterval = 10; // Seconds
        private const int RetrieveTweetsInterval = 30; // Seconds
        private const int DefaultMapZoomLevel = 19;
        private const int MaxMapZoomLevel = 20;
        private const int MinMapZoomLevel = 10;
        private const double MyLocationMarkerRelativeHeight = 0.06;
        public const double UserLocationMarkerRelativeHeight = 0.07;
        private const double ApplicationBarOpacity = 0.3;
        private String[] MyLocationMarkerImageUris = {
            "/Graphics/center-marker-red-48.png", // MyLocationMarkerTypes.Red
            "/Graphics/center-marker-green-48.png", // MyLocationMarkerTypes.Green
            "/Graphics/center-marker-grey-48.png" // MyLocationMarkerTypes.NoLocation
            };

        // Members
        private ApplicationSettings _settings = null;
        private Compass _compass = null;
        private TwitterManager _twitterManager = null;
        private GeoCoordinate _coordinate = null;
        private MapLayer _mapLayer = null;
        private List<User> _users = null;
        private Timer _locateTimer = null;
        private Timer _retrieveTweetsTimer = null;
        ApplicationBarIconButton _zoomInButton = null;
        ApplicationBarIconButton _zoomOutButton = null;
        ApplicationBarIconButton _tweetButton = null;
        private double _locationAccuracy = 0;
        private bool _locationFound = false;

        // Properties
        public static double ContentWidth
        {
            get;
            private set;
        }

        public static double ContentHeight
        {
            get;
            private set;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public MainPage()
        {
            InitializeComponent();

            ContentWidth = Application.Current.Host.Content.ActualWidth;
            ContentHeight = Application.Current.Host.Content.ActualHeight;

            AdjustItemsBasedOnContentSize();
            CreateRetroDisplayOverlayImage();

            _users = new List<User>();
            _settings = ApplicationSettings.GetInstance();
            _settings.SettingsChanged += new EventHandler(OnSettingsChanged);
            
            if (_settings.Load() && _settings.LocationAllowed)
            {
                // Settings loaded successfully
                LocationPanel.Visibility = Visibility.Collapsed;
                BuildLocalizedApplicationBar();
            }

            InitializeServicesBasedOnPermissions();

            _twitterManager = TwitterManager.GetInstance();
            _twitterManager.AuthenticationResult += new EventHandler<bool>(OnAuthenticationResult);
            _twitterManager.TweetingResult += new EventHandler<String>(OnTweetingResult);
            _twitterManager.LocationReceived += new EventHandler<User>(OnLocationReceived);
            _twitterManager.NewTweetMessageReceived += new EventHandler<String>(OnNewTweetMessageReceived);

            CreateMapItems();
        }

        /// <summary>
        /// Creates the application bar and its content.
        /// </summary>
        private void BuildLocalizedApplicationBar()
        {
            ApplicationBar = new ApplicationBar();
            ApplicationBar.Opacity = ApplicationBarOpacity;

            _zoomInButton = new ApplicationBarIconButton(new Uri("/Graphics/new.png", UriKind.Relative));
            _zoomInButton.Text = AppResources.ZoomInButtonText;
            _zoomInButton.Click += ZoomInButton_Click;
            _zoomInButton.IsEnabled = false;
            ApplicationBar.Buttons.Add(_zoomInButton);

            _zoomOutButton = new ApplicationBarIconButton(new Uri("/Graphics/minus.png", UriKind.Relative));
            _zoomOutButton.Text = AppResources.ZoomOutButtonText;
            _zoomOutButton.Click += ZoomOutButton_Click;
            _zoomOutButton.IsEnabled = false;
            ApplicationBar.Buttons.Add(_zoomOutButton);

            _tweetButton = new ApplicationBarIconButton(new Uri("/Graphics/twitter-icon-96.png", UriKind.Relative));
            _tweetButton.Text = AppResources.TweetButtonText;
            _tweetButton.Click += TweetButton_Click;
            _tweetButton.IsEnabled = false;
            ApplicationBar.Buttons.Add(_tweetButton);

            ApplicationBarMenuItem settingsMenuItem = new ApplicationBarMenuItem();
            settingsMenuItem.Text = "Settings"; //AppResources.SettingsMenuItemText;
            settingsMenuItem.Click += SettingsMenuIte_Click;
            ApplicationBar.MenuItems.Add(settingsMenuItem);

            ApplicationBarMenuItem aboutMenuItem = new ApplicationBarMenuItem();
            aboutMenuItem.Text = "About";
            aboutMenuItem.Click += AboutMenuIte_Click;
            ApplicationBar.MenuItems.Add(aboutMenuItem);
        }

        /// <summary>
        /// (Re)initializes the sensors and timers. Called when the app is
        /// launched and ready to function, when this page is resumed or
        /// when the settings have changed.
        /// </summary>
        private void InitializeServicesBasedOnPermissions()
        {
            if (Microsoft.Devices.Sensors.Compass.IsSupported)
            {
                // Setup and start the compass
                if (_compass == null)
                {
                    _compass = new Compass();
                    _compass.CurrentValueChanged +=
                        new EventHandler<SensorReadingEventArgs<CompassReading>>(
                            OnCompassReadingChanged);
                }

                try
                {
                    _compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(CompassUpdateInterval);
                    _compass.Start();
                }
                catch (InvalidOperationException e)
                {
                    MessageBox.Show(AppResources.FailedToStartCompassMessage
                        + " " + e.ToString());
                }
            }
            else if (_settings.FirstLaunch)
            {
                MessageBox.Show(AppResources.CompassNotSupportedMessage);
                _settings.FirstLaunch = false;
            }

            if (_locateTimer == null && _settings.LocationAllowed)
            {
                // Initialize timer
                _locateTimer = new Timer(GetCurrentLocation, null,
                    TimeSpan.FromSeconds(0),
                    TimeSpan.FromSeconds(LocationUpdateInterval));
            }
            else if (_locateTimer != null && !_settings.LocationAllowed)
            {
                _locateTimer.Dispose();
                _locateTimer = null;
            }

            if (_coordinate == null
                && _settings.LocationAllowed
                && _settings.LastLocation != null)
            {
                // Show the last (previous known) location on the map
                Debug.WriteLine("MainPage::InitializeServicesBasedOnPermissions(): Displaying the last known location.");
                _coordinate = _settings.LastLocation;
                MyMap.SetView(_coordinate, DefaultMapZoomLevel, MapAnimationKind.Parabolic);
            }

            if (_twitterManager != null
                && _twitterManager.IsAuthenticated
                && _retrieveTweetsTimer == null)
            {
                _retrieveTweetsTimer = new Timer(RetrieveTweets, null,
                    TimeSpan.FromSeconds(0),
                    TimeSpan.FromSeconds(RetrieveTweetsInterval));
            }
        }

        /// <summary>
        /// Creates a retro computer style overlay.
        /// </summary>
        private void CreateRetroDisplayOverlayImage()
        {
            RetroDisplayOverlayImage.Width = ContentWidth;
            RetroDisplayOverlayImage.Height = ContentHeight;

            WriteableBitmap bitmap = new WriteableBitmap(RetroDisplayOverlayImage, null);

            int width = bitmap.PixelWidth;
            int height = bitmap.PixelHeight;

            Debug.WriteLine("MainPage::CreateRetroDisplayOverlayImage(): ["
                + width + "x" + height + "]");

            int lineHeight = (int)Math.Round((double)height / 500.0);
            int temp = 0;

            int transparentBlue = 127 << 24 | 0 << 16 | 0 << 8 | 255;
            int black = 0 << 24 | 0 << 16 | 0 << 8 | 0;
            int current = transparentBlue;

            for (int y = 0; y < height; ++y)
            {
                if (temp == lineHeight)
                {
                    // Switch the color
                    current = (current == transparentBlue)
                        ? black : transparentBlue;
                    temp = 0;
                }

                temp++;

                for (int x = 0; x < width; ++x)
                {

                    bitmap.Pixels[y * width + x] = current;
                }
            }

            bitmap.Invalidate();
            RetroDisplayOverlayImage.Source = bitmap;
        }

        /// <summary>
        /// Adjusts the visual items to match the content size.
        /// </summary>
        private void AdjustItemsBasedOnContentSize()
        {
            MyLocationMarker.Width = ContentHeight * MyLocationMarkerRelativeHeight;
            MyLocationMarker.Height = MyLocationMarker.Width;
        }

        /// <summary>
        /// Creates the map layer.
        /// </summary>
        private void CreateMapItems()
        {
            if (_mapLayer == null)
            {
                // Create the map layer which will contain all the objects on
                // the map
                _mapLayer = new MapLayer();
                MyMap.Layers.Add(_mapLayer);
            }
        }

        /// <summary>
        /// Updates the items on the map.
        /// </summary>
        private void UpdateMapItems()
        {
            String name = null;
            MapOverlay overlay = null;

            foreach (var user in _users)
            {
                name = user.UserName;
                overlay = user.Overlay;

                if (overlay != null)
                {
                    Debug.WriteLine("MainPage::UpdateMapItems(): " + name);

                    if (!_mapLayer.Contains(overlay))
                    {
                        try
                        {
                            _mapLayer.Add(overlay);
                        }
                        catch (Exception e)
                        {
                            Debug.WriteLine("MainPage::UpdateMapItems(): " + e.ToString());
                        }
                    }
                }
                else
                {
                    Debug.WriteLine("MainPage::UpdateMapItems(): MapOverlay object for user '"
                        + user.UserName + "' is null!");
                }
            }
        }

        /// <summary>
        /// Tries to retrieve the current location. If UPD communication is
        /// allowed, the location is broadcasted to other users.
        /// </summary>
        /// <param name="state"></param>
        private async void GetCurrentLocation(Object state)
        {
            Geolocator geolocator = new Geolocator();
            geolocator.DesiredAccuracy = PositionAccuracy.High;

            try
            {
                Geoposition currentPosition =
                    await geolocator.GetGeopositionAsync(TimeSpan.FromMinutes(1),
                                                         TimeSpan.FromSeconds(10));
                _locationAccuracy = currentPosition.Coordinate.Accuracy;

                Dispatcher.BeginInvoke(() => OnLocationAcquired(currentPosition));
            }
            catch (Exception)
            {
                // Failed get the current location. Location might be disabled
                // in settings
                Debug.WriteLine("MainPage::GetCurrentLocation(): Failed to get current location!");
            }
        }

        /// <summary>
        /// Called when the current location has been acquired. Centers the map
        /// to the location.
        /// </summary>
        /// <param name="currentPosition">The current position of the user.</param>
        private void OnLocationAcquired(Geoposition currentPosition)
        {
            Debug.WriteLine("MainPage::OnLocationAcquired()");

            _coordinate =
                new GeoCoordinate(currentPosition.Coordinate.Latitude,
                                  currentPosition.Coordinate.Longitude);

            if (_locationFound == false)
            {
                SetCenterMarker((_settings.UserObjectType == User.ObjectType.Raider) ?
                    MyLocationMarkerTypes.Red : MyLocationMarkerTypes.Green);

                if (!_tweetButton.IsEnabled)
                {
                    _tweetButton.IsEnabled = true;
                }

                _locationFound = true;
            }

            double zoomLevel = MyMap.ZoomLevel < MinMapZoomLevel
                ? DefaultMapZoomLevel : MyMap.ZoomLevel;
            MapAnimationKind animationKind = MyMap.ZoomLevel < MinMapZoomLevel
                ? MapAnimationKind.Parabolic : MapAnimationKind.Linear;

            MyMap.SetView(_coordinate, zoomLevel, animationKind);

            UpdateMapItems();

            _settings.LastLocation = _coordinate;
            _settings.Save();
        }

        /// <summary>
        /// Tries to fetch the tweets containing the hashtag defined in the
        /// settings.
        /// </summary>
        /// <param name="state"></param>
        private void RetrieveTweets(object state)
        {
            Debug.WriteLine("MainPage::RetrieveTweets()");
            _twitterManager.SearchForUsers();
        }

        /// <summary>
        /// Sets the center marker image based on the given type.
        /// </summary>
        /// <param name="type">The type of the marker.</param>
        private void SetCenterMarker(MyLocationMarkerTypes type)
        {
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.UriSource = new Uri(MyLocationMarkerImageUris[(int)type], UriKind.Relative);
            MyLocationMarker.Source = bitmapImage;
            MyLocationMarker.Height = ContentHeight * MyLocationMarkerRelativeHeight;
        }

        /// <summary>
        /// Stops the timer and the compass.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            Debug.WriteLine("MainPage::OnNavigatedFrom(): Stopping services.");

            if (_locateTimer != null)
            {
                _locateTimer.Dispose();
                _locateTimer = null;
            }

            if (_compass != null)
            {
                _compass.Stop();
            }

            if (_retrieveTweetsTimer != null)
            {
                _retrieveTweetsTimer.Dispose();
                _retrieveTweetsTimer = null;
            }
        }

        /// <summary>
        /// (Re)starts the allowed services.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            Debug.WriteLine("MainPage::OnNavigatedTo(): (Re)starting services.");
            InitializeServicesBasedOnPermissions();
        }

        /// <summary>
        /// Updates the state of the zoom in/out buttons.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMapZoomLevelChanged(object sender, EventArgs e)
        {
            if (_zoomInButton == null || _zoomOutButton == null)
            {
                return;
            }

            if (Math.Round(MyMap.ZoomLevel) >= MaxMapZoomLevel)
            {
                _zoomInButton.IsEnabled = false;
            }
            else if (!_zoomInButton.IsEnabled)
            {
                _zoomInButton.IsEnabled = true;
            }

            if (MyMap.ZoomLevel <= MinMapZoomLevel)
            {
                _zoomOutButton.IsEnabled = false;
            }
            else if (!_zoomOutButton.IsEnabled)
            {
                _zoomOutButton.IsEnabled = true;
            }
        }

        /// <summary>
        /// Event handler for location usage permission at startup.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LocationUsage_Click(object sender, EventArgs e)
        {
            LocationPanel.Visibility = Visibility.Collapsed;
            BuildLocalizedApplicationBar();

            // The location setting is disallowed by default. Thus, there's no
            // need to do anything if cancel button is tapped.
            if (sender == AllowButton)
            {
                _settings.LocationAllowed = true;
                _settings.BroadcastingAllowed = true;
                _settings.Save();
            }

            InitializeServicesBasedOnPermissions();
        }

        /// <summary>
        /// Increases the zoom level of the map.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ZoomInButton_Click(object sender, EventArgs e)
        {
            if (MyMap.ZoomLevel < MaxMapZoomLevel)
            {
                double newZoomLevel = Math.Round(MyMap.ZoomLevel + 1);

                try
                {
                    MyMap.SetView(_coordinate, newZoomLevel, MapAnimationKind.Linear);
                }
                catch (ArgumentOutOfRangeException ex)
                {
                    Debug.WriteLine("MainPage::ZoomInButton_Click(): " + ex.ToString());
                }
            }
        }

        /// <summary>
        /// Decreases the zoom level of the map.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ZoomOutButton_Click(object sender, EventArgs e)
        {
            if (MyMap.ZoomLevel > MinMapZoomLevel)
            {
                MyMap.SetView(_coordinate, MyMap.ZoomLevel - 1, MapAnimationKind.Linear);
            }
        }

        /// <summary>
        /// Tries to tweet the user's current location. If the user has not
        /// authenticated, a Twitter login page is shown.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void TweetButton_Click(object sender, EventArgs e)
        {
            if (TwitterManager.GetInstance().IsAuthenticated
                && _settings.LocationAllowed
                && _coordinate != null)
            {
                CommonProgressBar.Visibility = Visibility.Visible;
                _twitterManager.SendLocation(_coordinate);
                _tweetButton.IsEnabled = false;                
            }
            else
            {
                // Not authenticated yet. Open the Twitter login page.
                NavigationService.Navigate(new Uri("/TwitterLoginPage.xaml", UriKind.Relative));
            }
        }

        /// <summary>
        /// Opens the about page.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void AboutMenuIte_Click(object sender, EventArgs e)
        {
            NavigationService.Navigate(new Uri("/AboutPage.xaml", UriKind.Relative));
        }

        /// <summary>
        /// Opens the settings page.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SettingsMenuIte_Click(object sender, EventArgs e)
        {
            NavigationService.Navigate(new Uri("/SettingsPage.xaml", UriKind.Relative));
        }

        /// <summary>
        /// Updates the map heading based on the current compass heading.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e">An object containing the compass readings.</param>
        private void OnCompassReadingChanged(object sender, SensorReadingEventArgs<CompassReading> e)
        {
            Dispatcher.BeginInvoke(()
                => MyMap.Heading = e.SensorReading.TrueHeading);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="user"></param>
        private void OnLocationReceived(object sender, User user)
        {
            if (user == null || user.UserName == null)
            {
                Debug.WriteLine("MainPage::OnLocationReceived(): The user or the user name is null!");
                return;
            }
            // Uncomment the following in case you do not want to show the
            // user's last tweeted location on the map.
            /*else if (user.UserName.Equals(_settings.UserName))
            {
                // It's me! Discard.
                return;
            }*/

            bool alreadyExists = false;

            foreach (var existingUser in _users)
            {
                if (existingUser.UserName != null
                    && existingUser.UserName.Equals(user.UserName))
                {
                    Debug.WriteLine("MainPage::OnLocationReceived(): User '"
                        + user.UserName + "' exists in the map. Updating the location...");
                    alreadyExists = true;
                    existingUser.Location = user.Location;

                    Dispatcher.BeginInvoke(() =>
                    {
                        _mapLayer.Remove(existingUser.Overlay);
                    });

                    break;

                }
            }

            if (!alreadyExists)
            {
                if (user.Location != null)
                {
                    Debug.WriteLine("MainPage::OnLocationReceived(): Creating new: '"
                        + user.UserName + "', " + user.Location.ToString());
                    _users.Add(user);
                }
                else
                {
                    Debug.WriteLine("MainPage::OnLocationReceived(): No location for user '"
                        + user.UserName + "'!");
                }
            }

            Dispatcher.BeginInvoke(() =>
            {
                UpdateMapItems();
            });
        }

        /// <summary>
        /// Called when the Twitter authentication attempt result is received.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="success">True if the user was authenticated
        /// successfully, false otherwise.</param>
        private void OnAuthenticationResult(Object sender, bool success)
        {
            Debug.WriteLine("MainPage::OnAuthenticationResult(): " + success);

            if (_settings.TwitterToken != null)
            {
                try
                {
                    // Set the user name if the user name is still the
                    // generated integer.
                    int.Parse(_settings.UserName); // This will fail if the user name is not an integer
                    _settings.UserName = _settings.TwitterToken.ScreenName;
                }
                catch (Exception)
                {
                }
            }

            _retrieveTweetsTimer = new Timer(RetrieveTweets, null,
                TimeSpan.FromSeconds(0),
                TimeSpan.FromSeconds(RetrieveTweetsInterval));

            // Send my location
            _twitterManager.SendLocation(_coordinate);
            _tweetButton.IsEnabled = false;
        }

        /// <summary>
        /// Called when the result of tweet sending attempt is received.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="errorMessage">Null if the tweet was sent successfully,
        /// will contain an error message otherwise.</param>
        private void OnTweetingResult(object sender, String errorMessage)
        {
            Debug.WriteLine("MainPage::OnTweetingResult(): "
                + ((errorMessage == null) ? "Ok!" : errorMessage));

            Dispatcher.BeginInvoke(() =>
            {
                _tweetButton.IsEnabled = true;
                CommonProgressBar.Visibility = Visibility.Collapsed;
                MessageBox.Show((errorMessage == null)
                    ? AppResources.TweetSentSuccessfullyMessage
                    : AppResources.FailedToSendTweetMessage + " " + errorMessage);
            });
        }

        /// <summary>
        /// Updates the text block to contain the latest tweet.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="message"></param>
        private void OnNewTweetMessageReceived(object sender, String message)
        {
            TweetTextBlock.Text = message.ToUpper();
        }

        /// <summary>
        /// Applies the changed settings.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnSettingsChanged(object sender, EventArgs e)
        {
            InitializeServicesBasedOnPermissions();

            if (_locationFound)
            {
                SetCenterMarker((_settings.UserObjectType == User.ObjectType.Raider) ?
                    MyLocationMarkerTypes.Red : MyLocationMarkerTypes.Green);
            }
        }
    }
}